/**
 * 
 */
package org.xtense.model;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xtense.log.iLog;
import org.xtense.model.actions.AttributeInterceptor;
import org.xtense.model.actions.ElementInterceptor;
import org.xtense.model.actions.ResultCache;

/**
 * @author Owner
 * 
 */
public class CSubSet {

	private HashMap<Integer, CElement> elements;
	private HashMap<Integer, List<CAttribute>> attributes;
	private HashMap<Integer, CText> text;
	private HashMap<Integer, PathEntry> path;
	private List<Node> resultAfterPacked = null;
	private ElementInterceptor eleInter;
	private AttributeInterceptor attInter;
	// if this is for reading, id = -1
	private int documentID = -1;
	
	public int getDocumentID() {
		return documentID;
	}

	public void setDocumentID(int documentID) {
		this.documentID = documentID;
	}

	protected CSubSet() {
		elements = new HashMap<Integer, CElement>();
		attributes = new HashMap<Integer, List<CAttribute>>();
		text = new HashMap<Integer, CText>();
		path = new HashMap<Integer, PathEntry>();
		resultAfterPacked = null;
		eleInter = new ElementInterceptor();
		attInter = new AttributeInterceptor();
	}
	
	public CSubSet(int documentId) {
		this();
		this.documentID = documentId;
		eleInter = new ElementInterceptor();
		attInter = new AttributeInterceptor();
	}

	/**
	 * @param attributes
	 * @param elements
	 * @param text
	 */
	protected CSubSet(HashMap<Integer, List<CAttribute>> attributes,
			HashMap<Integer, CElement> elements, HashMap<Integer, CText> text,
			int documentID) {
		this.attributes = attributes;
		this.elements = elements;
		this.text = text;
		this.documentID = documentID;
		eleInter = new ElementInterceptor();
		attInter = new AttributeInterceptor();
	}

	/**
	 * Add an element, using for translating from a data set.
	 * 
	 * @param cElement
	 */
	public void addElement(CElement cElement) {
		int elementPre = cElement.getElementPre();
		elements.put(elementPre, cElement);

	}

	/**
	 * Add an attribute, using for translating from a data set.
	 * 
	 * @param cAttribute
	 */
	public void addAttribute(CAttribute cAttribute) {
		int elementPre = cAttribute.getElementPre();
		List<CAttribute> list;
		if (attributes.containsKey(elementPre)) {
			list = attributes.get(elementPre);
		} else {
			list = new ArrayList<CAttribute>();
		}
		list.add(cAttribute);
		attributes.put(elementPre, list);
	}

	/**
	 * Add text,using for translating from a data set.
	 * 
	 * @param cText
	 */
	public void addText(CText cText) {
		int elementPre = cText.getElementPre();
		if (text.containsKey(elementPre)) {
			text.put(elementPre, text.get(elementPre).combine(cText));
		} else {
			text.put(elementPre, cText);
		}

	}

	public List<Node> getResultSet() {
		if (resultAfterPacked == null) {
			iLog.info("Pack the result to elements.");
			resultAfterPacked = new ArrayList<Node>();
			if (elements.size() != 0) {
				HashMap<Integer, Element> allElements = new HashMap<Integer, Element>();
				Integer[] keys = elements.keySet().toArray(
						new Integer[elements.keySet().size()]);
				int[] sortedKeys = new int[keys.length];
				for (int i = 0; i < keys.length; i++) {
					sortedKeys[i] = keys[i].intValue();
				}
				Arrays.sort(sortedKeys);
				for (int i = 0; i < sortedKeys.length; i++) {
					CElement cElement = elements.get(sortedKeys[i]);
					Element element = getElementWithAttributeAndText(cElement);
					allElements.put(sortedKeys[i], element);
					int parentPre = cElement.getParentPre();
					if (allElements.containsKey(parentPre)) {
						allElements.get(parentPre).add(element);
					} else {
						resultAfterPacked.add(element);
					}
				}
			} else {
				Iterator<List<CAttribute>> itr = attributes.values().iterator();
				while (itr.hasNext()) {
					for (CAttribute ca : itr.next()) {
						resultAfterPacked.add(getAttribute(ca));
					}
				}
			}
			startTransaction();
		}

		return resultAfterPacked;
	}

	private Attribute getAttribute(CAttribute cAttribute) {
		Attribute att = attInter.createAttribute(cAttribute
				.getAttributeName(), cAttribute.getAttributeValue());
		ResultCache.cacheAttribute(att, getDocumentID(), cAttribute);
		return att;
	}

	private Element getElementWithAttributeAndText(CElement cElement) {
		boolean forQuery = documentID > 0;
		Element element = eleInter.createElement(cElement.getElementTag());
		int elementPre = cElement.getElementPre();
		// add attributes
		if (attributes.containsKey(elementPre)) {
			List<CAttribute> atts = attributes.get(elementPre);
			Iterator<CAttribute> i = atts.iterator();
			while (i.hasNext()) {
				CAttribute ca = i.next();
				Attribute att = attInter.createAttribute(ca
						.getAttributeName(), ca.getAttributeValue());
				element.add(att);
				if (forQuery)
					ResultCache.cacheAttribute(att, documentID, ca);
			}
		}
		// add text
		if (text.containsKey(elementPre)) {
			CText ct = text.get(elementPre);
			element.addText(ct.getTextContent());
		}
		if (forQuery) {
			ResultCache.cacheElement(element, documentID, cElement);
			ResultCache.cachePath(element, path.get(elementPre).getPathValue());
		}
			
		return element;
	}

	public boolean isCompleteDocument() {
		return getResultSet().size() == 1
				&& getResultSet().get(0) instanceof Element;
	}

	public void prettyWrite() throws UnsupportedEncodingException, IOException {
		OutputFormat format = OutputFormat.createPrettyPrint();
		XMLWriter writer = new XMLWriter(System.out, format);
		List<Node> eles = getResultSet();
		Iterator<Node> iterator = eles.iterator();
		while (iterator.hasNext()) {
			writer.write(iterator.next());
		}
	}

	public void compactWrite() throws UnsupportedEncodingException, IOException {
		OutputFormat format = OutputFormat.createCompactFormat();
		XMLWriter writer = new XMLWriter(System.out, format);
		List<Node> eles = getResultSet();
		Iterator<Node> iterator = eles.iterator();
		while (iterator.hasNext()) {
			writer.write(iterator.next());
		}
	}

	public void compactWrite(OutputStream os)
			throws UnsupportedEncodingException, IOException {
		OutputFormat format = OutputFormat.createCompactFormat();
		XMLWriter writer = new XMLWriter(os, format);
		List<Node> eles = getResultSet();
		Iterator<Node> iterator = eles.iterator();
		while (iterator.hasNext()) {
			writer.write(iterator.next());
		}
	}

	/**
	 * @return the elements
	 */
	public HashMap<Integer, CElement> getElements() {
		return elements;
	}

	/**
	 * @param elements
	 *            the elements to set
	 */
	protected void setElements(HashMap<Integer, CElement> elements) {
		this.elements = elements;
	}

	/**
	 * @return the attributes
	 */
	public HashMap<Integer, List<CAttribute>> getAttributes() {
		return attributes;
	}

	/**
	 * @param attributes
	 *            the attributes to set
	 */
	protected void setAttributes(HashMap<Integer, List<CAttribute>> attributes) {
		this.attributes = attributes;
	}

	/**
	 * @return the text
	 */
	public HashMap<Integer, CText> getText() {
		return text;
	}

	/**
	 * @param text
	 *            the text to set
	 */
	protected void setText(HashMap<Integer, CText> text) {
		this.text = text;
	}

	private void startTransaction() {
		eleInter.setTransaction(true);
		attInter.setTransaction(true);
	}
	
	/**
	 * @return the path
	 */
	public Collection<PathEntry> getPath() {
		return path.values();
	}

	public boolean addPath(PathEntry e) {
		path.put(e.getElmentPre(), e);
		return true;
	}

}
